<?php
/**
* Media library stuff.
*
* Copyright (c) 2009 James Gauld <james@jamesgauld.net>
* This file is part of Scribble.
* @license http://www.scribblecms.co.uk/license.txt
*
* @package Scribble
*/
use Buan\Config;
use Buan\ExtensionManager;
use Buan\Model;
use Buan\ModelCriteria;
use Buan\ModelManager;
use Buan\SystemLog;
use Buan\UrlCommand;
class MediaLibraryController extends ScribbleAdminController {

	/**
	* Handles the browsing, uploading and removal of all media assets.
	*
	* @param array Action parameters
	* @return View
	*/
	public function index($params) {

		// Prepare views
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/media-library/index.tpl.php');
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/media-library/index.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Auth: Determine user's "media-author" privilege
		$pMediaAuthor = ScribbleAuth::authorize(ScribbleSession::getUser(), 'media-author');

		// Build requested path
		$imgFolderRoot = $imgFolderCurrent = Config::get('ext.Scribble.uploads.dir');
		$imgUrlRoot = Config::get('ext.Scribble.uploads.url');
		$parentFolder = NULL;
		$currentFolder = NULL;
		if(isset($_GET['f']) && preg_match("/^\/{1}.+/", $_GET['f'])) {
			$_GET['f'] = preg_replace("/^\/*/", "/", preg_replace("/\./ium", "", urldecode($_GET['f'])));
			$imgFolderCurrent .= $_GET['f'];

			// Selection is a directory
			if(is_dir($imgFolderCurrent)) {
				$parentFolder = str_replace($imgFolderRoot, "", dirname($imgFolderCurrent));
				$currentFolder = str_replace($imgFolderRoot, "", $imgFolderCurrent);
			}

			// Selection is a file (should never be called)
			else if(is_file($imgFolderCurrent)) {
				SystemLog::add("If you're reading this, you most probably have Javascript disabled. Please enable it!", SystemLog::FATAL);
				$view->setSource(NULL);
				return $view;
			}

			// Unknown folder
			else {
				SystemLog::add("Unknown folder.", SystemLog::FATAL);
				$view->setSource(NULL);
				return $view;
			}
		}

		// Process: Delete folder/file
		if(isset($_POST['method']) && $_POST['method']=='delete' && isset($_POST['df'])) {

			// Auth: Is user allowed to delete files/folders
			$_POST['df'] = preg_replace("/^\/*/", "/", preg_replace("/\.{2,}/ium", "", urldecode($_POST['df'])));
			$df = "{$imgFolderRoot}{$_POST['df']}";
			if(!$pMediaAuthor) {
				die('You do not have permission to remove folders/files from the media library.');
			}
			else if(!file_exists($df)) {
				die("File not found");
			}
			else if(is_dir($df)) {
				$s = scandir($df);
				if(count($s)>2) {
					die("Cannot removed an non-empty folder");
				}
				else if(!rmdir($df)) {
					die("Failed to remove folder");
				}
				else {
					die("OK");
				}
			}
			else if(is_file($df) && !unlink($df)) {
				die("Failed to remove file");
			}
			else {
				die("OK");
			}
		}

		// Process: Create new folder
		if(isset($_POST['dn'])) {

			// Auth: Can user create new folders?
			$_POST['dn'] = preg_replace("/\//", "", preg_replace("/^\.+/ium", "", urldecode($_POST['dn'])));
			if(!$pMediaAuthor) {
				SystemLog::add('You do not have permission to create new folders in the media library.', SystemLog::WARNING);
			}
			else if($_POST['dn']!='') {
				$newFolder = $imgFolderCurrent.'/'.$_POST['dn'];
				if(!file_exists($newFolder)) {
					if(!mkdir($newFolder, 0777)) {
						SystemLog::add('Failed to create the folder. Please check permissions on the parent folder and try again.', SystemLog::WARNING);
					}
					else {
						session_write_close();
						header("Location: ".UrlCommand::createUrl('scribble', 'media-library', array('f'=>$_GET['f'])), TRUE, 303);
						exit(0);
					}
				}
			}
		}

		// Process: Upload files
		if(isset($_FILES['fn']) && $_FILES['fn']['error']==0 && $_FILES['fn']['size']>0) {

			// Auth: Can user upload files?
			$fn = $_FILES['fn'];
			$fn['name'] = preg_replace("/\//", "", preg_replace("/^\.+/ium", "", urldecode($fn['name'])));
			$newFile = $imgFolderCurrent.'/'.$fn['name'];
			if(!$pMediaAuthor) {
				SystemLog::add('You do not have permission to upload new files to the media library.', SystemLog::WARNING);
			}
			else if(!move_uploaded_file($fn['tmp_name'], $newFile)) {
				SystemLog::add('Failed to upload file, please try again.', SystemLog::WARNING);
			}
			else {
				chmod($newFile, 0777);
			}
		}

		// Load files
		$items = new StdClass();
		$items->folders = array();
		$items->files = array();
		$resources = new DirectoryIterator($imgFolderCurrent);
		foreach($resources as $resource) {
			if($resource->isDot() || preg_match("/^\./", $resource->getFilename())) {
				continue;
			}

			$item = new StdClass();
			$item->relativeUrl = str_replace('\\', '/', str_replace($imgFolderRoot, '', $resource->getPathName()));
			$item->relativeUrlNoSlash = preg_replace("/^\/+/", "", $item->relativeUrl);
			$item->absoluteUrl = $imgUrlRoot.$item->relativeUrl;

			if($resource->isDir()) {
				$items->folders[] = $item;
			}

			if($resource->isFile()) {
				$items->files[] = $item;
			}
		}

		// Result
		$view->items = $items;
		$view->parentFolder = $parentFolder;
		$view->currentFolder = $currentFolder;
		return $view;
	}

	/**
	* The media-selector widget. This is rendered within a popup dialog window.
	*
	* The following GET parameters are supported:
	* string callback	= Name of js function that handles callabcks from the widget
	* string path		= Initial path loaded into the widget (inc. upload folder prefix)
	* string data		= JSON-encoded content used to pre-populate the UI
	* int smax			= Max no. items that can be selected (defaults to 1)
	* string stype	= Valid file types, eg "jpg,gif" (default to "*")
	* string view		= Initial view layout (list | thumbnails)
	*
	* @return ScribbleAdminView
	*/
	public function browserWidget() {

		// Prepare views
		$view = new ScribbleAdminView();
		$view->popup();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/media-library/browser-widget.tpl.php');

		// Gather widget config options
		$config = new StdClass();
		$config->uploadPathPrefix = ExtensionManager::get('Scribble')->getUploadPath()->url;
		$config->path = isset($_GET['path']) ? $_GET['path'] : $config->uploadPathPrefix;
		$config->callback = isset($_GET['callback']) ? $_GET['callback'] : '';
		$config->data = isset($_GET['data']) ? $_GET['data'] : '';
		$config->smax = isset($_GET['smax']) && $_GET['smax']>0 ? (int)$_GET['smax'] : 1;
		$config->stype = !isset($_GET['stype']) ? array('*') : (
			is_array($_GET['stype']) ? $_GET['stype'] : explode(",", $_GET['stype'])
		);
		$config->view = isset($_GET['view']) && in_array($_GET['view'], array('list', 'thumbnails')) ? $_GET['view'] : 'list';
		$view->config = $config;

		// Handle AJAX calls
		if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])=='xmlhttprequest') {
			$this->browserWidgetAjax($config);
			exit();
		}

		// Result
		return $view;
	}

	/**
	* Handles various AJAX calls relating to the browser widget.
	*
	* @param StdClass Widget config
	* @return void
	*/
	private function browserWidgetAjax($config) {

		// Method: Load files
		if(isset($_POST['method']) && $_POST['method']=='load') {

			// Prepare the path
			// The passed path includes the upload folder repfix, so strip that first
			$path = preg_replace("/^".preg_quote($config->uploadPathPrefix, '/')."/", "", $_POST['path']);
			$path = preg_replace("/\/+$/", '', $path);

			// Prepare response
			$uploadPath = ExtensionManager::get('Scribble')->getUploadPath();
			$dirPath = "{$uploadPath->dir}{$path}";
			$urlPath = "{$uploadPath->url}{$path}";
			$feed = new StdClass();
			$feed->url = $urlPath;
			$feed->assets = new StdClass();
			$feed->assets->folders = array();
			$feed->assets->files = array();
			$feed->errorCode = 0;
			$feed->errorMessage = '';

			// Invalid path

			// Unknown path
			if(!file_exists($dirPath) || !is_readable($dirPath)) {
				$feed->errorCode = '1';
				$feed->errorMessage = 'Requested path cannot be read';
			}

			// Build list of assets
			else {
				$dirIgnore = Config::get('app.dir.ignored');
				$assets = new DirectoryIterator($dirPath);
				foreach($assets as $asset) {
					if($asset->isDot() || in_array($asset->getFilename(), $dirIgnore)) {
						// Don't add dot folders or system folders
					}
					else if($asset->isDir()) {
						$feed->assets->folders[] = array(
							'filename'=>$asset->getFilename(),
							'datemodified'=>$asset->getMTime(),
							'url'=>"{$urlPath}/{$asset->getFilename()}"
						);
					}
					else {
						$feed->assets->files[] = array(
							'filename'=>$asset->getFilename(),
							'filesize'=>scribble_formatFilesize($asset->getSize()),
							'datemodified'=>$asset->getMTime(),
							'url'=>"{$urlPath}/{$asset->getFilename()}",
							'filecategory'=>$this->determineFileCategory($asset->getPathname())
						);
					}
				}
			}

			// Response
			echo json_encode($feed);
			exit(0);
		}
	}

	/**
	* Given a file name, work out which category it falls into. One of:
	* image, document, audio, video
	*
	* @param string Full path to file
	* @return string
	*/
	private function determineFileCategory($path) {
		if(preg_match("/\.([^\.\/\\\\]+)$/", $path, $m)) {
			$ext = $m[1];
			if(in_array($ext, explode(",", 'gif,png,jpg,jpeg,bmp,tiff,tif'))) {
				return "image";
			}
		}
		else {
			return "unknown";
		}
	}

	/**
	* UI for modifying folder settings.
	*
	* @return ScribbleAdminView
	*/
	public function folder() {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/media-library/folder.tpl.php');

		// Check folder is valid
		$scribble = ExtensionManager::get('Scribble');
		if(!isset($_GET['f']) || !is_dir($scribble->getUploadPath()->dir."/".urldecode($_GET['f']))) {
			SystemLog::add('Invalid folder.', SystemLog::WARNING);
			return new ScribbleAdminView();
		}
		$htaccessFile = $scribble->getUploadPath()->dir."/".urldecode($_GET['f'])."/.htaccess";
		$view->folder = urldecode($_GET['f']);

		// Generate folder hash which can be used as a "resource ID" for auth stuff
		// The hash is taken of the full folder path RELATIVE TO THE UPLOAD FOLDER
		$folderId = md5(urldecode($_GET['f']));
		$view->folderId = $folderId;

		// Process: Save
		if(!empty($_POST)) {

			// Gather data
			$folderSecurity = isset($_POST['security']) ? $_POST['security'] : 'inherit';
			if($folderSecurity!='auth') {
				unset($_POST['privilege']);
			}
			$privileges = ScribbleAuthWidget::gatherPrivilegesFromPost($folderId, isset($_POST['privilege']) ? $_POST['privilege'] : array());

			// Copy the approprate .htaccess file into the folder, or remove it (in
			// the case of "inherit")
			if($folderSecurity==='inherit' && file_exists($htaccessFile)) {
				unlink($htaccessFile);
			}
			else if($folderSecurity==='allow-all') {
				copy(Config::get('ext.Scribble.dir.resources').'/.htaccess-allow-all', $htaccessFile);
				chmod($htaccessFile, 0777);
			}
			else if($folderSecurity==='auth') {
				$src = Config::get('ext.Scribble.dir.resources').'/.htaccess-media-auth';
				$code = str_replace("{{VIEW_AUTH_URL}}", UrlCommand::createUrl('scribble', 'media-library', 'view-auth'), file_get_contents($src));
				file_put_contents($htaccessFile, $code);
				chmod($htaccessFile, 0777);
			}

			// Save privileges
			ScribbleAuthWidget::saveGatheredPrivileges($folderId, $privileges);
			SystemLog::add('Folder saved.', SystemLog::INFO);
		}

		// Determine if the folder inherit settings, allows all, or requires auth
		$folderSecurity = 'inherit';
		if(!file_exists($htaccessFile)) {
			$folderSecurity = 'inherit';
		}
		else if(($c = file_get_contents($htaccessFile)) && preg_match("/ALLOW_ALL/m", $c)) {
			$folderSecurity = 'allow-all';
		}
		else if(($c = file_get_contents($htaccessFile)) && preg_match("/MEDIA_AUTH/m", $c)) {
			$folderSecurity = 'auth';
		}
		$view->folderSecurity = $folderSecurity;

		// Gather privileges data
		$view->privilegeData = ScribbleAuthWidget::getResourcePrivileges($folderId, 'media-folder-edit');

		// Result
		return $view;
	}

	/**
	* Deliver the requested file only if the currently active user has the
	* necessary privilege to do so.
	*
	* Requests for files that are under privilege control are routed through this
	* action.
	*
	* @return mixed
	*/
	public function viewAuth() {

		// Ensure the requested file is actually in the uploads path, and determine
		// if user has "media-folder-view" privilege on the requested folder
		$file = urldecode($_SERVER['REQUEST_URI']);
		$folder = dirname($file);
		$scribble = ExtensionManager::get('Scribble');
		$uploadPath = $scribble->getUploadPath();
		$folderId = md5(preg_replace("/^".preg_quote($uploadPath->url, '/')."/", "", $folder));
		if(!preg_match("/^".preg_quote($uploadPath->url, '/')."/", $folder)) {
			header("HTTP/1.1 500");
			die("Invalid file.");
		}
		else if(!ScribbleSession::isOccupied()) {
			header("HTTP/1.1 403");
			die("Access denied");
		}
		else if(!ScribbleAuth::authorize(ScribbleSession::getUser(), 'media-folder-view', $folderId)) {
			header("HTTP/1.1 403");
			die("Access denied");
		}

		// User has permission to view files in this folder, so deliver the file
		session_write_close();
		$file = $uploadPath->dir.preg_replace("/^".preg_quote($uploadPath->url, '/')."/", "", $file);
		try {
			$finfo = new \finfo(FILEINFO_MIME_TYPE);
		}
		catch(Exception $e) {
			header("HTTP/1.1 500");
			die("FileInfo PHP extension is not available on this system.");
		}
		if(!$finfo) {
			header("HTTP/1.1 500");
			die("Failed to read file correctly. Please inform system administrator.");
		}
		$mimetype = $finfo->file($file);
		header("Content-Type: {$mimetype}");
		$fp = fopen($file, "rb");
		fpassthru($fp);
		exit(0);
	}
}
?>